<?php

namespace app\index\service\payments;

use app\common\job\TradeRefund;
use app\common\model\AccountBill;
use app\common\model\Trade;
use app\common\service\AccountBillService;
use app\common\service\TemplateSmsService;
use ShidianUtil\Concurrent\Synchronizer;
use ShidianUtil\Net\IHttpClient;
use ShidianUtil\Utils\StringUtil;
use think\exception\ValidateException;
use think\facade\Config;
use think\facade\Log;
use think\facade\Queue;
use think\Request;

/**
 * 抖音担保支付
 * Class TtpayPaymentService
 * @package app\index\service\payments
 */
class TtpayPaymentService implements PaymentInterface, PaymentNotifyInterface
{
    private Synchronizer $synchronizer;
    private IHttpClient $httpClient;

    public function __construct(Synchronizer $synchronizer, IHttpClient $httpClient)
    {
        $this->synchronizer = $synchronizer;
        $this->httpClient = $httpClient;
    }

    // **************** 支付 ****************/

    /**
     * 支付预下单
     * @param Trade $trade
     * @return array
     */
    public function pay(Trade $trade, bool $arrears = false): array
    {
        // 创建抖音支付预下单订单，组装参数给前端用
        $config = Config::get('pay.ttpay', []);
        $app_id = $config['app_id'];
        $out_order_no = $trade->orderNo;
        $total_amount = (int)bcmul($trade->payMoney, 100);
        $subject = '熊猫律师咨询';
        $body = $subject;
        $valid_time = $trade->payExpTime - time();
        $notify_url = $config['notify_url'] ?? '';

        $post = [
            'app_id' => $app_id,
            'out_order_no' => $out_order_no,
            'total_amount' => $total_amount,
            'subject' => $subject,
            'body' => $body,
            'valid_time' => $valid_time,
            // 'sign' => $sign,
            'notify_url' => $notify_url,
        ];

        $post['sign'] = $this->sign($post);

        $url = 'https://developer.toutiao.com/api/apps/ecpay/v1/create_order';

        // 生成抖音支付预付单
        try {

            $response = $this->httpClient->postJSONFormat($url, [], [], $post);

            $result = json_decode((string)$response->getBody(), true);
        } catch (\Exception $e) {

            Log::error('抖音下单失败' . $e->getMessage());

            throw new ValidateException('下单失败');
        }

        // 判断返回值是否正确
        $err_no = $result['err_no'] ?? null;
        if ($err_no !== 0 && $err_no !== '0') {
            Log::error('抖音下单失败err_no不对');
            throw new ValidateException('下单失败');
        }

        return $result['data'];
    }

    /**
     * 支付回调
     * @param Request $request
     * @return array|mixed|string[]
     */
    public function payCallback(Request $request)
    {
        $body = $request->param();

        Log::info('抖音支付回调参数：' . json_encode($body));

        // 验签
        $verifiedStatus = $this->verifiedSign($body);
        if (!$verifiedStatus) {
            Log::error('抖音支付回调，验签失败');
            return $this->response(1, '验签失败');
        }

        // 解密参数
        $params = $this->decodeParams($body['msg'] ?? null);

        $total_fee = $params['total_amount'] ?? 0;
        $total_fee = bcdiv($total_fee, 100, 2);
        $orderNo = $params['cp_orderno'] ?? null;
        $transaction_id = $params['order_id'] ?? '';
        $status = $params['status'] ?? null;

        // 支付结果是不成功
        if ($status !== 'SUCCESS') {
            Log::warning('抖音支付，回调的支付结果不是成功');
            return $this->response(2, '结果不是成功');
        }

        if (is_blank($orderNo)) {
            Log::warning('抖音支付，回调没有本地单号');
            return $this->response(3, '没有本地单号');
        }

        return $this->paySuccess($orderNo, $transaction_id, $total_fee);
    }

    /**
     * 取消预下单
     * @param Trade $trade
     * @return void
     */
    public function cancelPay(Trade $trade): void
    {
        // 抖音没有取消预下单的接口
        return;
    }

    /**
     * 主动查询支付结果
     * @param Trade $trade
     * @return void
     */
    public function query(Trade $trade): void
    {

        $config = Config::get('pay.ttpay', []);
        $app_id = $config['app_id'];
        $out_order_no = $trade->orderNo;

        $post = [
            'app_id' => $app_id,
            'out_order_no' => $out_order_no,
        ];

        $post['sign'] = $this->sign($post);

        $url = 'https://developer.toutiao.com/api/apps/ecpay/v1/query_order';

        // 查询抖音支付订单
        try {

            $response = $this->httpClient->postJSONFormat($url, [], [], $post);

            $result = json_decode((string)$response->getBody(), true);
        } catch (\Exception $e) {

            Log::error('抖音查询订单失败' . $e->getMessage());

            throw new ValidateException('查询失败');
        }

        // 判断返回值是否正确
        $err_no = $result['err_no'] ?? null;

        if ($err_no !== 0 && $err_no !== '0') {
            Log::error('抖音查询订单失败err_no不对');
            throw new ValidateException('查询失败');
        }

        $order_status = $result['payment_info']['order_status'] ?? null;
        $orderNo = $result['out_order_no'] ?? '';
        $transaction_id = $result['order_id'];
        $total_fee = $result['payment_info']['total_fee'] ?? 0;
        $total_fee = bcdiv($total_fee, 100, 2);

        // 支付结果是不成功
        if ($order_status !== 'SUCCESS') {
            return;
        }

        $this->paySuccess($orderNo, $transaction_id, $total_fee);
    }

    // **************** 退款 ****************/

    /**
     * 发起退款
     * @param Trade $trade
     * @return bool
     */
    public function refund(Trade $trade): bool
    {
        // 只有未开始和已失败可以发起退款
        if (!in_array($trade->refundResult, [Trade::UNSTART, Trade::FAIL])) {
            return false;
        }

        // 进入退款中状态
        $trade->refundResult = Trade::STARTED;
        if (is_blank($trade->refundNo)) {
            $trade->refundNo = 'REFUND' . $trade->orderNo;
        }
        $trade->save();

        $config = Config::get('pay.ttpay', []);
        $app_id = $config['app_id'];
        $out_order_no = $trade->orderNo;
        $out_refund_no = $trade->refundNo;
        $reason = $trade->refundReason;
        $refund_amount = (int)bcmul($trade->payMoney, 100);
        $notify_url = $config['refund_notify_url'] ?? '';

        $post = [
            'app_id' => $app_id,
            'out_order_no' => $out_order_no,
            'out_refund_no' => $out_refund_no,
            'reason' => $reason,
            'refund_amount' => $refund_amount,
            'notify_url' => $notify_url,
        ];

        $post['sign'] = $this->sign($post);

        $url = 'https://developer.toutiao.com/api/apps/ecpay/v1/create_refund';

        // 发起退款
        try {

            $response = $this->httpClient->postJSONFormat($url, [], [], $post);

            $result = json_decode((string)$response->getBody(), true);

            $err_no = $result['err_no'] ?? null;

            if ($err_no === 0 || $err_no === '0') {

                return true;
            }

            // 余额不足需要提醒管理员
            if ($err_no == 4105 || $err_no == 4202) {
                $this->getTemplateSmsService()->wechatMoneyNotEnough();
            }

            // 三方转账结果保存为待开始，方便重试
            $trade->refundResult = Trade::UNSTART;
            $trade->save();

            return false;
        } catch (\Exception $e) {

            Log::error('抖音退款请求失败' . $e->getMessage());

            $trade->refundResult = Trade::UNSTART;
            $trade->save();

            return false;
        }
    }

    // 退款回调
    public function refundCallback(Request $request)
    {
        $body = $request->param();

        Log::info('抖音退款回调参数：' . json_encode($body));

        // 验签
        $verifiedStatus = $this->verifiedSign($body);
        if (!$verifiedStatus) {
            Log::error('抖音退款回调，验签失败');
            return $this->response(1, '验签失败');
        }

        // 解密参数
        $params = $this->decodeParams($body['msg'] ?? null);

        $refund_fee = $params['refund_amount'] ?? 0;
        $refund_fee = bcdiv($refund_fee, 100, 2);
        $refundNo = $params['cp_refundno'] ?? null;
        $refund_id = $params['refund_no'] ?? '';
        $status = $params['status'] ?? null;

        // 使用退款单号查询订单
        $trade = Trade::where('refundNo', $refundNo)->find();
        if (!$trade) {
            Log::warning('抖音支付，退款回调找不到订单');
            return $this->response(2, '找不到订单');
        }

        $orderNo = $trade->orderNo;

        if (is_blank($orderNo)) {
            Log::warning('抖音支付，退款回调没有本地单号');
            return $this->response(3, '缺少本地单号');
        }

        $success = false;
        $trade = null;

        $signature = Trade::class . $orderNo;
        $this->synchronizer->synchronized($signature, function () use (&$success, &$trade, $orderNo, $refund_id, $status) {

            $trade = Trade::where('orderNo', $orderNo)->find();

            if (!$trade) {
                return;
            }

            if ($trade->refundStatus != Trade::REFUNDING || $trade->refundResult != Trade::STARTED) {
                return;
            }

            // 保存订单状态
            $trade->refundNid = $refund_id;
            $trade->refundTime = time();
            if (StringUtil::equals($status, 'SUCCESS')) {
                $trade->refundStatus = Trade::REFUNDED;
                $trade->refundResult = Trade::SUCCESS;
            } else {
                $trade->refundResult = Trade::FAIL;
            }
            $trade->save();

            $success = true;
        });

        if (!$success) {
            Log::warning('抖音支付，回调订单不存在或状态不正确');
            return $this->response(4, '回调订单不存在或状态不正确');
        }

        if (StringUtil::equals($status, 'SUCCESS')) {
            // 回调结果是成功

            $tradeId = $trade->id;
            $tradeType = $trade->tradeType;
            $describe = $trade->describe;

            // 查询该订单的支付账单
            $sourceId = AccountBill::where('tradeId', $tradeId)
                ->where('type', 'pay')
                ->where('billType', $tradeType)
                ->value('id');

            // 插入账单
            $this->getAccountBillService()->insertRefundThird($trade->memberId, '', PAYMENT_WXPAY, $refund_fee, $tradeType, $describe, $tradeId, $sourceId);

        } else {
            // 回调结果是失败

            Log::error('消费队列：抖音退款回调结果失败，重新添加退款任务，订单ID=' . $trade->id);

            // 重新发起退款，添加退款任务
            $job_data = ['id' => $trade->id];
            Queue::push(TradeRefund::class, $job_data);
        }


        return $this->response();
    }

    /**
     * 转账是否可用
     * @param $memberId
     * @return bool
     */
    public function isEnableTransfer($memberId): bool
    {
        return false;
    }

    private function response($err_no = 0, $err_tips = 'success')
    {
        return [
            'err_no' => $err_no,
            'err_tips' => $err_tips,
        ];
    }

    /**
     * 签名
     * @param $map
     * @return string
     */
    private function sign($map)
    {
        $salt = Config::get('pay.ttpay.salt');

        $rList = [];

        foreach ($map as $k => $v) {
            if ($k == 'other_settle_params' || $k == 'app_id' || $k == 'sign' || $k == 'thirdparty_id') {
                continue;
            }

            $value = trim(strval($v));

            $len = strlen($value);

            if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"") {

                $value = substr($value, 1, $len - 1);
            }

            $value = trim($value);

            if ($value == '' || $value == 'null') {
                continue;
            }

            $rList[] = $value;
        }

        $rList[] = $salt;

        sort($rList, 2);

        return md5(implode('&', $rList));
    }

    /**
     * 验签
     * @param $body
     * @return bool
     */
    private function verifiedSign($body): bool
    {
        $token = Config::get('pay.ttpay.token');

        $msg_signature = $body['msg_signature'] ?? '';

        $filtered = [];
        foreach ($body as $key => $value) {
            if (in_array($key, ['msg_signature', 'type'])) {
                continue;
            }
            $filtered[] = is_string($value) ? trim($value) : $value;
        }
        $filtered[] = trim($token);
        sort($filtered, SORT_STRING);
        $filtered = trim(implode('', $filtered));

        return StringUtil::equals(sha1($filtered), $msg_signature);
    }

    private function decodeParams($inBody): array
    {
        if (is_blank($inBody)) {
            return [];
        }
        return json_decode($inBody, true);
    }


    private function getAccountBillService(): AccountBillService
    {
        return app(AccountBillService::class);
    }

    private function getTemplateSmsService(): TemplateSmsService
    {
        return app(TemplateSmsService::class);
    }

    /**
     * 支付成功结果处理
     * @param $orderNo
     * @param $transaction_id
     * @param float $total_fee
     * @return array
     */
    private function paySuccess($orderNo, $transaction_id, $total_fee): array
    {
        $success = false;
        $trade = null;

        $signature = Trade::class . $orderNo;
        $this->synchronizer->synchronized($signature, function () use (&$success, &$trade, $orderNo, $transaction_id) {

            $trade = Trade::where('orderNo', $orderNo)->find();

            if (!$trade) {
                return;
            }

            if ($trade->payStatus != Trade::UNPAID) {
                return;
            }

            $trade->payMemberType = 'member';
            $trade->payMemberId = $trade->memberId;

            // 保存已支付的状态
            $trade->tradeNid = $transaction_id;
            $trade->payStatus = Trade::PAID;
            $trade->payTime = time();
            $trade->save();

            $success = true;
        });

        if (!$success) {
            if (!$trade) {
                Log::warning('抖音支付，回调本地订单不存在');
                return $this->response(4, '本地订单不存在');
            }
            Log::warning('抖音支付，回调本地订单状态不正确 ID ' . $trade->id . ' payStatus' . $trade->payStatus);
            return $this->response(4, '本地订单状态不正确');
        }

        // 插入账单
        $tradeId = $trade->id;
        $tradeType = $trade->tradeType;
        $describe = $trade->describe;
        $this->getAccountBillService()->insertPayThird($trade->memberId, '', PAYMENT_TTPAY, $total_fee, $tradeType, $describe, $tradeId);

        // 触发支付成功事件
        event('PaySuccess', $trade);

        return $this->response();
    }
}
